using System;
using System.Collections.Generic;
using UnityEngine;

public class MaterialComplexityCache : ScriptableObject
{
    [SerializeField] private List<MaterialComplexityInfo> m_MaterialComplexityInfos = new List<MaterialComplexityInfo>();

    public struct MaterialComplexityKey
    {
        public string m_Shader;
        public List<string> m_Keywords;
        public string m_GPU;
    }
    private Dictionary<MaterialComplexityKey, MaterialComplexityInfo> m_ComplexityMap = new Dictionary<MaterialComplexityKey, MaterialComplexityInfo>(new ComplexityMapComparer());
    
    public class ComplexityMapComparer : IEqualityComparer<MaterialComplexityKey>
    {
        public bool Equals(MaterialComplexityKey x, MaterialComplexityKey y)
        {
            if (x.m_Shader == y.m_Shader && x.m_GPU == y.m_GPU)
            {
                bool keywordsEqual = x.m_Keywords.Count == y.m_Keywords.Count;
                if (keywordsEqual)
                {
                    foreach (string keyword in x.m_Keywords)
                    {
                        if (!y.m_Keywords.Contains(keyword))
                        {
                            keywordsEqual = false;
                        }
                    }
                }

                return keywordsEqual;
            }
            return false;
        }

        public int GetHashCode(MaterialComplexityKey obj)
        {
            string combineKey = obj.m_Shader + obj.m_GPU;
            foreach (string keyword in obj.m_Keywords)
            {
                combineKey += keyword;
            }
            return combineKey.GetHashCode();
        }
    }

    public Dictionary<MaterialComplexityKey, MaterialComplexityInfo> ComplexityMap
    {
        get
        {
            if (m_ComplexityMap.Count == 0)
            {
                foreach (MaterialComplexityInfo complexityInfo in m_MaterialComplexityInfos)
                {
                    MaterialComplexityKey key = new MaterialComplexityKey()
                    {
                        m_Shader = complexityInfo.m_Shader,
                        m_GPU = complexityInfo.m_GPU,
                        m_Keywords = complexityInfo.m_Keywords
                    };
                    if (m_ComplexityMap.ContainsKey(key))
                    {
                        continue;
                    }
                    m_ComplexityMap.Add(key, complexityInfo);
                }
            }
            return m_ComplexityMap;
        }
    }

    public void AddComplexityInfo(Material mat, string gpu, float complexity, string bound)
    {
        foreach (MaterialComplexityInfo complexityInfo in m_MaterialComplexityInfos)
        {
            if (complexityInfo.m_Shader == mat.shader.name && complexityInfo.m_GPU == gpu)
            {
                bool keywordsEqual = mat.shaderKeywords.Length == complexityInfo.m_Keywords.Count;
                if (keywordsEqual)
                {
                    foreach (string keyword in mat.shaderKeywords)
                    {
                        if (!complexityInfo.m_Keywords.Contains(keyword))
                        {
                            keywordsEqual = false;
                        }
                    }
                }

                if (keywordsEqual)
                {
                    Debug.LogError($"[MaterialComplexityCache.AddComplexityInfo] The cache contains a info with same key -> " +
                                   $"material: {mat.name}, complexity: {complexityInfo.m_Complexity}, bound: {complexityInfo.m_Bound}");
                    return;
                }
            }
        }
        MaterialComplexityInfo materialComplexityInfo = new MaterialComplexityInfo()
        {
            m_Shader = mat.shader.name,
            m_GPU = gpu,
            m_Complexity = complexity,
            m_Bound = bound
        };
        materialComplexityInfo.m_Keywords.AddRange(mat.shaderKeywords);
        
        m_MaterialComplexityInfos.Add(materialComplexityInfo);
        m_ComplexityMap.Clear();
    }

    public bool TryGetMaterialComplexityInfo(Material mat, string gpu, out MaterialComplexityInfo complexityInfo)
    {
        MaterialComplexityKey key = new MaterialComplexityKey()
        {
            m_Shader = mat.shader.name,
            m_Keywords = new List<string>(mat.shaderKeywords),
            m_GPU = gpu
        };
        return ComplexityMap.TryGetValue(key, out complexityInfo);
    }
}

[Serializable]
public class MaterialComplexityInfo
{
    public string m_Shader;
    public List<string> m_Keywords = new List<string>();
    public string m_GPU;
    public float m_Complexity;
    public string m_Bound;
}